package org.springframework.beans.factory.context;


import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.PostProcessorRegistrationDelegate;

public abstract class AbstractApplicationContext implements ConfigurableApplicationContext {

    private long startupDate;

    private final Object startupShutdownMonitor = new Object();

    private Thread shutdownHook;

    private final AtomicBoolean active = new AtomicBoolean();

    private final AtomicBoolean closed = new AtomicBoolean();

    private String id = UUID.randomUUID().toString();

    private final List<BeanFactoryPostProcessor> beanFactoryPostProcessors = new ArrayList<>();

    @Override
    public long getStartupDate() {
        return this.startupDate;
    }

    @Override
    public void refresh(){
        synchronized (this.startupShutdownMonitor) {
            //准备startupDate active closed
            prepareRefresh();

            DefaultListableBeanFactory beanFactory = obtainFreshBeanFactory();
            //空
            prepareBeanFactory(beanFactory);
            //空
            postProcessBeanFactory(beanFactory);

            try {
                //执行bean工厂后置处理器，主要做解析配置类config相关
                invokeBeanFactoryPostProcessors(beanFactory);

                //注册AutowiredAnnotationBeanPostProcessor和 CommonAnnotationBeanPostProcessor
                registerBeanPostProcessors(beanFactory);

                //遍历所有bd，提前预加载单例化
                finishBeanFactoryInitialization(beanFactory);

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    protected void finishBeanFactoryInitialization(DefaultListableBeanFactory beanFactory) throws Exception {
        beanFactory.preInstantiateSingletons();
    }

    protected void registerBeanPostProcessors(DefaultListableBeanFactory beanFactory) throws Exception {
        PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory);
    }


    protected void invokeBeanFactoryPostProcessors(DefaultListableBeanFactory beanFactory) throws Exception {
        PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory,getBeanFactoryPostProcessors());
    }

    protected void postProcessBeanFactory(DefaultListableBeanFactory beanFactory){

    }

    protected void prepareBeanFactory(DefaultListableBeanFactory beanFactory){

    }

    public List<BeanFactoryPostProcessor> getBeanFactoryPostProcessors() {
        return this.beanFactoryPostProcessors;
    }

    protected DefaultListableBeanFactory obtainFreshBeanFactory() {
        refreshBeanFactory();
        return getBeanFactory();
    }

    protected void prepareRefresh(){
        this.startupDate = System.currentTimeMillis();
        this.closed.set(false);
        this.active.set(true);
    }

    @Override
    public void registerShutdownHook() {
        if (this.shutdownHook == null) {
            // No shutdown hook registered yet.
            this.shutdownHook = new Thread() {
                @Override
                public void run() {
                    synchronized (startupShutdownMonitor) {
                        doClose();
                    }
                }
            };
            Runtime.getRuntime().addShutdownHook(this.shutdownHook);
        }
    }

    @Override
    public void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor) {
        beanFactoryPostProcessors.add(postProcessor);
    }

    @Override
    public void close() {
        synchronized (this.startupShutdownMonitor) {
            doClose();
            // If we registered a JVM shutdown hook, we don't need it anymore now:
            // We've already explicitly closed the context.
            if (this.shutdownHook != null) {
                try {
                    Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
                }
                catch (IllegalStateException ex) {
                    // ignore - VM is already shutting down
                }
            }
        }
    }

    protected void doClose() {
        if (this.active.get() && this.closed.compareAndSet(false, true)) {

            destroyBeans();

            closeBeanFactory();

            this.active.set(false);
        }
    }

    protected  void destroyBeans(){
        getBeanFactory().destroySingletons();
    }
    protected abstract void closeBeanFactory();

    protected abstract void refreshBeanFactory();

    protected void cancelRefresh() {
        this.active.set(false);
    }
    @Override
    public String getId() {
        return id;
    }

    @Override
    public void setId(String id) {
        this.id = id;
    }

    @Override
    public boolean isActive() {
        return this.active.get();
    }

    @Override
    public Object getBean(String beanName) throws Exception {
        return getBeanFactory().getBean(beanName);
    }

    @Override
    public boolean containsBean(String beanName) {
        return getBeanFactory().containsBean(beanName);
    }
}